#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <pthread.h>
#include <time.h>
#include <curl/curl.h>

#include "curlEasy.h"

// Reset all settings and then put our defaults on
void resetCurlSettings(CURL* _passedCurl){
	curl_easy_reset(_passedCurl);
	// To use https
	curl_easy_setopt(_passedCurl, CURLOPT_SSL_VERIFYPEER, 1);
	// Disable console output stuff we don't need
	curl_easy_setopt(_passedCurl, CURLOPT_VERBOSE, 0L);
	curl_easy_setopt(_passedCurl, CURLOPT_NOPROGRESS, 1L);
	// Probably don't even need this
	curl_easy_setopt(_passedCurl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
	curl_easy_setopt(_passedCurl, CURLOPT_FOLLOWLOCATION, 1L);
}
void globalInitCurl(){
	curl_global_init(CURL_GLOBAL_ALL);
}
void globalDeleteCurl(){
	curl_global_cleanup();
}
CURL* newCurl(){
	CURL* _returnHandle = curl_easy_init();
	resetCurlSettings(_returnHandle);
	return _returnHandle;
}
void deleteCurl(CURL* _passedCurl){
	curl_easy_cleanup(_passedCurl);
}

/*====================================================================================================================================*/

// Callback for curl to add the data to memory
size_t curlWriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp){
	size_t realsize = size * nmemb;
	memoryStruct* mem = (memoryStruct*) userp;

	mem->memory = realloc(mem->memory, mem->size + realsize + 1);
	if(mem->memory == NULL) {
		/* out of memory! */
		printf("not enough memory (realloc returned NULL)\n");
		return 0;
	}

	memcpy(&(mem->memory[mem->size]), contents, realsize);
	mem->size += realsize;
	mem->memory[mem->size] = 0;

	return realsize;
}
// Callback for curl to discard data the website sends back to us
size_t curlWriteDiscardData(void *ptr, size_t size, size_t nmemb, void *stream){
	return size*nmemb;
}
// Callback for curl to write the data to a file
size_t curlWriteDataFile(void *ptr, size_t size, size_t nmemb, void *stream){
	return fwrite(ptr, size, nmemb, (FILE *)stream);
}

/*====================================================================================================================================*/
/*
// (currentbytes - lastbytes)/((currenttime - lasttime));
int _curlPrintProgressBar(void* ptr, curl_off_t _downloadGoal, curl_off_t _amoutDownloaded, curl_off_t _a, curl_off_t _b){
	progressFunctionInfo* _passedPointer = (progressFunctionInfo*)ptr;
	//if (*(_passedPointer->shouldPrint)==1){
		if (_downloadGoal!=0){ // Prevent divide by 0
			printf("%ld/%ld;%d%%",_amoutDownloaded,_downloadGoal,(int)((_amoutDownloaded/(double)_downloadGoal)*100));
			// Get time
			struct timespec _timeStruct;
			clock_gettime(CLOCK_REALTIME, &_timeStruct);
			long _newDividedClock = (int)(_timeStruct.tv_nsec / 1.0e6)+_timeStruct.tv_sec*1000;
			// Avoid divide by 0
			if (_passedPointer->lastDividedClock!=0){
				//printf("\n\namoutDownloaded:%ld\nlastDownloaded:%ld\nnewSeconds:%ld\nlastSeconds:%ld\n\n",_amoutDownloaded,_passedPointer->lastDownloaded,_newDividedClock,_passedPointer->lastDividedClock);
				printf(";%d kbps\n",(int)(((_amoutDownloaded-_passedPointer->lastDownloaded)/((double)(_newDividedClock-_passedPointer->lastDividedClock)/(1000)))/1000));
			}else{
				printf("\n");
			}
			_passedPointer->lastDividedClock = _newDividedClock;
			_passedPointer->lastDownloaded = _amoutDownloaded;
		}else{
			printf("Init...\n");
		}
		//(((progressFunctionInfo*)ptr)->shouldPrint)=0;
	//}
	return 0;
}
*/
/*====================================================================================================================================*/

struct curl_slist *newCurlHeaders(){
	// This is correct
	return NULL;
}
void curlSetContentType(char* _passedType, struct curl_slist** _passedHeaders){
	char _totalContentString[strlen("Content-Type: ")+strlen(_passedType)+1];
		strcpy(_totalContentString,"Content-Type: ");
		strcat(_totalContentString,_passedType);
	*_passedHeaders = curl_slist_append(*_passedHeaders, _totalContentString);
}
void freeCurlHeaders(struct curl_slist* _passedHeaders){
	curl_slist_free_all(_passedHeaders);
}
// Call right before you start downloading the file or whatever.
void applyCurlHeaders(CURL* _passedCurl, struct curl_slist* _passedHeaders){
	curl_easy_setopt(_passedCurl, CURLOPT_HTTPHEADER, _passedHeaders);
}

// Get webpage data and put it into memory. It will be null terminated
void downloadWebpageData(CURL* _passedCurl, memoryStruct* chunkToDownloadTo, char* url){
	CURLcode res;
	// Init passed struct
	chunkToDownloadTo->memory = malloc(1);  /* will be grown as needed by the realloc above */
	chunkToDownloadTo->memory[0]='\0';
	chunkToDownloadTo->size = 0;    /* no data at this point */
	// Set up curl
	curl_easy_setopt(_passedCurl, CURLOPT_URL, url);
	curl_easy_setopt(_passedCurl, CURLOPT_WRITEFUNCTION, curlWriteMemoryCallback);
	curl_easy_setopt(_passedCurl, CURLOPT_WRITEDATA, (void*)chunkToDownloadTo);
	res = curl_easy_perform(_passedCurl);
	if(res != CURLE_OK) {
		printf("Failed to get webpage data.\n");
	}
}

/*#define CONTENTLENGTHHEADERFORMAT "Content-Length: %d"
void setCurlPostHeaders(char* _postData, struct curl_slist** _passedHeaders){
	*_passedHeaders = curl_slist_append(*_passedHeaders, "Expect: 100-continue");
	char _lengthHeadString[strlen(CONTENTLENGTHHEADERFORMAT)+10+1]; // Up to 12 digit number
		sprintf(_lengthHeadString,CONTENTLENGTHHEADERFORMAT,strlen(_postData));
	printf("%s\n",_lengthHeadString);
	*_passedHeaders = curl_slist_append(*_passedHeaders, _lengthHeadString);
}*/

void setUpCurlPost(CURL* _passedCurl, char* _postData){
	curl_easy_setopt(_passedCurl, CURLOPT_POSTFIELDSIZE, strlen(_postData)); // Must be before adding POST data
	curl_easy_setopt(_passedCurl, CURLOPT_COPYPOSTFIELDS, _postData);
	curl_easy_setopt(_passedCurl, CURLOPT_POST, 1L);
}


int _downloadUpdateProgress(void* ptr, curl_off_t downloadGoal, curl_off_t amoutDownloaded, curl_off_t _a, curl_off_t _b){
	long _newMilliseconds = getMilliseconds();
	downloadInstance* _passedInfo = ptr;
	if (_newMilliseconds<_passedInfo->lastMilliseconds+500){
		return 0;
	}
	_passedInfo->downloadGoal = downloadGoal;
	_passedInfo->downloadSpeed=(curl_off_t)(((amoutDownloaded-_passedInfo->lastDownloaded)/((double)(_newMilliseconds-_passedInfo->lastMilliseconds)/((double)1000)))/DOWNLOADSPEEDDIVISOR);
	_passedInfo->lastDownloaded = amoutDownloaded;
	_passedInfo->lastMilliseconds=_newMilliseconds;
	return 0;
}
long getMilliseconds(){
	struct timespec _timeStruct;
	clock_gettime(CLOCK_REALTIME, &_timeStruct);
	return (long)(_timeStruct.tv_nsec / 1.0e6)+_timeStruct.tv_sec*1000;
}
char downloadFileBlocking(CURL* _passedCurl, char* passedUrl, char* passedFilename){
	FILE* fp;
	curl_easy_setopt(_passedCurl, CURLOPT_URL, passedUrl);
	fp = fopen(passedFilename, "wb");
	if(fp) {
		curl_easy_setopt(_passedCurl, CURLOPT_WRITEFUNCTION, curlWriteDataFile);
		curl_easy_setopt(_passedCurl, CURLOPT_WRITEDATA, fp);
		if (curl_easy_perform(_passedCurl)!=CURLE_OK){
			printf("Failed to download to file.\n");
		}
		fclose(fp);
		return 1;
	}else{
		printf("Failed to open file %s.\n",passedFilename);
		return 0;
	}
}

void* _downloadFileSyncThread(void* _passedData){
	downloadInstance* _myDownloadInfo = _passedData;
	downloadFileBlocking(_myDownloadInfo->_personalCURL,_myDownloadInfo->url,_myDownloadInfo->destFile);
	_myDownloadInfo->_isDone=1;
	return NULL;
}

// Start downloading a file on a different thread
void downloadFileSync(downloadInstance* _passedDownloadInfo){
	//_passedDownloadInfo->destFile = strdup(passedFilename);
	//_passedDownloadInfo->url = strdup(passedUrl);
	_passedDownloadInfo->_isDone=0;
	_passedDownloadInfo->downloadGoal=1;
	_passedDownloadInfo->downloadSpeed=0;
	_passedDownloadInfo->lastMilliseconds = getMilliseconds();
	_passedDownloadInfo->lastDownloaded = 0;
	_passedDownloadInfo->_personalCURL = newCurl();

	resetCurlSettings(_passedDownloadInfo->_personalCURL);
	curl_easy_setopt(_passedDownloadInfo->_personalCURL, CURLOPT_NOPROGRESS, 0); // Internal CURL progressmeter must be disabled if we provide our own callback
	curl_easy_setopt(_passedDownloadInfo->_personalCURL, CURLOPT_XFERINFOFUNCTION, _downloadUpdateProgress); // Install the callback function
	curl_easy_setopt(_passedDownloadInfo->_personalCURL, CURLOPT_XFERINFODATA, _passedDownloadInfo);

	pthread_t _newDownloadThread;
	pthread_create(&_newDownloadThread,NULL,_downloadFileSyncThread,_passedDownloadInfo);
}